#include "stdinc.h"
#include <Ogre.h>
#include <OgreFont.h>
#include <OgreFontManager.h>
#include <CEGUI.h>
#include <CEGUIXMLAttributes.h>
#include <CEGUIXMLHandler.h>
#include <CEGUIXMLParser.h>
#include "Main/Logger.h"
#include "MaterialAtlas.h"
#include "DwarfFort/InternalStructures.h"

#define _DEBUG_SAVE_ATLAS 1

using namespace Ogre;
// helper function from the wiki
void WriteToTexture(const String &str, HardwarePixelBufferSharedPtr destBuffer, Image::Box destRectangle, Font* font, const ColourValue &color, char justify = 'l',  bool wordwrap = true)
{
	using namespace Ogre;

/*	if (destTexture->getHeight() < destRectangle.bottom)
		destRectangle.bottom = destTexture->getHeight();
	if (destTexture->getWidth() < destRectangle.right)
		destRectangle.right = destTexture->getWidth();*/

	if (!font->isLoaded())
		font->load();

	TexturePtr fontTexture = (TexturePtr) TextureManager::getSingleton().getByName(font->getMaterial()->getTechnique(0)->getPass(0)->getTextureUnitState(0)->getTextureName());
	
	HardwarePixelBufferSharedPtr fontBuffer = fontTexture->getBuffer();
	//HardwarePixelBufferSharedPtr destBuffer = destTexture->getBuffer();

	PixelBox destPb = destBuffer->lock(destRectangle,HardwareBuffer::HBL_NORMAL);
	
        // The font texture buffer was created write only...so we cannot read it back :o). One solution is to copy the buffer  instead of locking it. (Maybe there is a way to create a font texture which is not write_only ?)
        
        // create a buffer
        size_t nBuffSize = fontBuffer->getSizeInBytes();
        uint8* buffer = (uint8*)calloc(nBuffSize, sizeof(uint8)); 
        
        // create pixel box using the copy of the buffer
        PixelBox fontPb(fontBuffer->getWidth(), fontBuffer->getHeight(),fontBuffer->getDepth(), fontBuffer->getFormat(), buffer);          
        fontBuffer->blitToMemory(fontPb);

	uint8* fontData = static_cast<uint8*>( fontPb.data );
	uint8* destData = static_cast<uint8*>( destPb.data );

	const size_t fontPixelSize = PixelUtil::getNumElemBytes(fontPb.format);
	const size_t destPixelSize = PixelUtil::getNumElemBytes(destPb.format);

	const size_t fontRowPitchBytes = fontPb.rowPitch * fontPixelSize;
	const size_t destRowPitchBytes = destPb.rowPitch * destPixelSize;

	Box *GlyphTexCoords;
	GlyphTexCoords = new Box[str.size()];

	Font::UVRect glypheTexRect;
	size_t charheight = 0;
	size_t charwidth = 0;

	for(unsigned int i = 0; i < str.size(); i++)
	{
		if ((str[i] != '\t') && (str[i] != '\n') && (str[i] != ' '))
		{
			glypheTexRect = font->getGlyphTexCoords(str[i]);
			GlyphTexCoords[i].left = glypheTexRect.left * fontTexture->getSrcWidth();
			GlyphTexCoords[i].top = glypheTexRect.top * fontTexture->getSrcHeight();
			GlyphTexCoords[i].right = glypheTexRect.right * fontTexture->getSrcWidth();
			GlyphTexCoords[i].bottom = glypheTexRect.bottom * fontTexture->getSrcHeight();

			if (GlyphTexCoords[i].getHeight() > charheight)
				charheight = GlyphTexCoords[i].getHeight();
			if (GlyphTexCoords[i].getWidth() > charwidth)
				charwidth = GlyphTexCoords[i].getWidth();
		}

	}	

	size_t cursorX = 0;
	size_t cursorY = 0;
	size_t lineend = destRectangle.getWidth();
	bool carriagreturn = true;
	for (unsigned int strindex = 0; strindex < str.size(); strindex++)
	{
		switch(str[strindex])
		{
		case ' ': cursorX += charwidth;  break;
		case '\t':cursorX += charwidth * 3; break;
		case '\n':cursorY += charheight; carriagreturn = true; break;
		default:
			{
				//wrapping
				if ((cursorX + GlyphTexCoords[strindex].getWidth()> lineend) && !carriagreturn )
				{
					cursorY += charheight;
					carriagreturn = true;
				}
				
				//justify
				if (carriagreturn)
				{
					size_t l = strindex;
					size_t textwidth = 0;	
					size_t wordwidth = 0;

					while( (l < str.size() ) && (str[l] != '\n)'))
					{		
						wordwidth = 0;

						switch (str[l])
						{
						case ' ': wordwidth = charwidth; ++l; break;
						case '\t': wordwidth = charwidth *3; ++l; break;
						case '\n': l = str.size();
						}
						
						if (wordwrap)
							while((l < str.size()) && (str[l] != ' ') && (str[l] != '\t') && (str[l] != '\n'))
							{
								wordwidth += GlyphTexCoords[l].getWidth();
								++l;
							}
						else
							{
								wordwidth += GlyphTexCoords[l].getWidth();
								l++;
							}
	
						if ((textwidth + wordwidth) <= destRectangle.getWidth())
							textwidth += (wordwidth);
						else
							break;
					}

					if ((textwidth == 0) && (wordwidth > destRectangle.getWidth()))
						textwidth = destRectangle.getWidth();

					switch (justify)
					{
					case 'c':	cursorX = (destRectangle.getWidth() - textwidth)/2;
							lineend = destRectangle.getWidth() - cursorX;
							break;

					case 'r':	cursorX = (destRectangle.getWidth() - textwidth);
							lineend = destRectangle.getWidth();
							break;

					default:	cursorX = 0;
							lineend = textwidth;
							break;
					}

					carriagreturn = false;
				}

				//abort - net enough space to draw
				if ((cursorY + charheight) > destRectangle.getHeight())
					goto stop;

				//draw pixel by pixel
				for (size_t i = 0; i < GlyphTexCoords[strindex].getHeight(); i++ )
					for (size_t j = 0; j < GlyphTexCoords[strindex].getWidth(); j++)
					{
 						float alpha =  color.a * (fontData[(i + GlyphTexCoords[strindex].top) * fontRowPitchBytes + (j + GlyphTexCoords[strindex].left) * fontPixelSize +1 ] / 255.0);
 						float invalpha = 1.0 - alpha;
 						size_t offset = (i + cursorY) * destRowPitchBytes + (j + cursorX) * destPixelSize;
  						ColourValue pix;
 						PixelUtil::unpackColour(&pix,destPb.format,&destData[offset]);
 						pix = (pix * invalpha) + (color * alpha);
 						PixelUtil::packColour(pix,destPb.format,&destData[offset]);
  					}
 
				cursorX += GlyphTexCoords[strindex].getWidth();
			}//default
		}//switch
	}//for

stop:
	delete[] GlyphTexCoords;

	destBuffer->unlock();
	
        // Free the memory allocated for the buffer
        free(buffer); buffer = 0;
}















enum eBits
{	
	BIT_0 = 1 << 0,
	BIT_1 = 1 << 1,
	BIT_2 = 1 << 2,
	BIT_3 = 1 << 3,
	BIT_4 = 1 << 4,
	BIT_5 = 1 << 5,
	BIT_6 = 1 << 6,
	BIT_7 = 1 << 7
};
struct BlendMM
{
	char i[256],j[256],k[256],l[256];
	BlendMM()
	{
		int i,j,k,l;
		for (int c = 0 ; c< 256; c++)
		{
			i = j = k = l = 0;
			// i corner
			if ((c & BIT_1) && (c & BIT_7)) // filled corner
				i = 4;
			else
			if (c & BIT_1)				   // one side only
				i = 2;
			else
			if (c & BIT_7)				   // one side only
				i = 1;
			else
			if (c & BIT_0)				   //corner
				i = 3;
			// j corner
			if ((c & BIT_5) && (c & BIT_7))// filled corner
				j = 4;
			else
			if (c & BIT_5)				   // one side only
				j = 2;
			else
			if (c & BIT_7)				   // one side only
				j = 1;
			else
			if (c & BIT_6)				   //corner
				j = 3;
			// k corner
			if ((c & BIT_1) && (c & BIT_3))// filled corner
				k = 4;
			else
			if (c & BIT_1)				   // one side only
				k = 2;
			else
			if (c & BIT_3)				   // one side only
				k = 1;
			else
			if (c & BIT_2)				   //corner
				k = 3;
			// l corner
			if ((c & BIT_3) && (c & BIT_5))// filled corner
				l = 4;
			else
			if (c & BIT_5)				   // one side only
				l = 2;
			else
			if (c & BIT_3)				   // one side only
				l = 1;
			else
			if (c & BIT_4)				   //corner
				l = 3;
			this->i[c] = i;
			this->j[c] = j;
			this->k[c] = k;
			this->l[c] = l;
		}
	}
} gBlendMM;



namespace Graphics
{

	class MatglossXMLHandler :public CEGUI::XMLHandler
		{
			std::hacked_vector<DwarfFort::Matgloss*> &mDefinitions;
			std::vector<MaterialDef *> &mOutput;
			MaterialDef * mCurrentDef;
			std::string mElement;
		public:
			MatglossXMLHandler (std::hacked_vector<DwarfFort::Matgloss*> &definitions,
								std::vector<MaterialDef *> &output);

			void elementStart(const CEGUI::String& element, const CEGUI::XMLAttributes& attributes);
            void elementEnd(const CEGUI::String& element);
			void text(const CEGUI::String& text);
		};


};
using namespace Graphics;

MaterialAtlas::MaterialAtlas(std::hacked_vector<DwarfFort::Matgloss*> &definitions,std::string textureList)
:mDefinitions(definitions),mTextureListFile(textureList)
{
}


MaterialAtlas::~MaterialAtlas()
{
}

void MaterialAtlas::loadDefinitions()
{
	CEGUI::XMLParser *pars = CEGUI::System::getSingleton().getXMLParser();
	MatglossXMLHandler handler(mDefinitions,mTextures);
	pars->parseXMLFile(handler,mTextureListFile,"Materials.xsd","");
}


void MaterialAtlas::generateBlendMap()
{
	std::string group = Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;  
	mBlendMap = Ogre::TextureManager::getSingleton().create(this->mTextureListFile+"blendMap",group,true);
	mBlendMap->setWidth(1024);
	mBlendMap->setHeight(1024);
	mBlendMap->setFormat(Ogre::PixelFormat::PF_R8G8B8A8);
	mBlendMap->setNumMipmaps(1);
	mBlendMap->createInternalResources();

	Ogre::Image *buildblox[4][5];

	const int mapDim = 64; // 1024/16

	buildblox[0][0] = new Ogre::Image();
	buildblox[0][0]->load(std::string("blend_map_0.png"),group);
	buildblox[0][1] = new Ogre::Image();
	buildblox[0][1]->load(std::string("blend_map_1.png"),group);
	buildblox[0][2] = new Ogre::Image();
	buildblox[0][2]->load(std::string("blend_map_2.png"),group);
	buildblox[0][3] = new Ogre::Image();
	buildblox[0][3]->load(std::string("blend_map_3.png"),group);
	buildblox[0][4] = new Ogre::Image();
	buildblox[0][4]->load(std::string("blend_map_4.png"),group);

	
	for (int i = 0; i < 5; i++)
	{
		buildblox[1][i] = new Ogre::Image(*buildblox[0][i]);
		buildblox[1][i]->flipAroundY();
		buildblox[2][i] = new Ogre::Image(*buildblox[0][i]);
		buildblox[2][i]->flipAroundX();
		buildblox[3][i] = new Ogre::Image(*buildblox[2][i]);
		buildblox[3][i]->flipAroundY();
	}

	Ogre::HardwarePixelBufferSharedPtr buf = mBlendMap->getBuffer();
	//Ogre::PixelBox transfer(40,40,buf->getDepth(),mBlendMap->getFormat(),new char[40*40*4]); 
	
	for (int c = 0; c < 256; c++)
	{
		int x1 = (c/16)*mapDim;
		int y1 = (c%16)*mapDim;
		int x2 = x1+mapDim;
		int y2 = y1+mapDim;
		int h = mapDim/2;

		Ogre::Image::Box _1(x1,y1,x2-h,y2-h);
		buf->blitFromMemory(buildblox[0][gBlendMM.i[c]]->getPixelBox(),_1); 
		Ogre::Image::Box _3(x1+h,y1,x2,y2-h);
		buf->blitFromMemory(buildblox[1][gBlendMM.j[c]]->getPixelBox(),_3); 
		Ogre::Image::Box _2(x1,y1+h,x2-h,y2);
		buf->blitFromMemory(buildblox[2][gBlendMM.k[c]]->getPixelBox(),_2); 
		Ogre::Image::Box _4(x1+h,y1+h,x2,y2);
		buf->blitFromMemory(buildblox[3][gBlendMM.l[c]]->getPixelBox(),_4); 
	}
	for (int i = 0; i < 5 ; i++)
	{
		delete buildblox[0][i];
		delete buildblox[1][i];
		delete buildblox[2][i];
		delete buildblox[3][i];
	}
	//delete[] transfer.data;
	Ogre::Image result;
	
	result.loadDynamicImage((Ogre::uchar  *)buf->lock(0,buf->getSizeInBytes(),Ogre::HardwareBuffer::LockOptions::HBL_READ_ONLY),buf->getWidth(),buf->getHeight(),buf->getDepth(),buf->getFormat());
	
	result.save(std::string("media/materials/textures/blendMap.png"));  
	buf->unlock();

}



void MaterialAtlas::build()
{
	std::vector<MaterialDef *> defsToBuild;
	std::vector<MaterialDef *>::iterator i;

	for (i = mTextures.begin(); i != mTextures.end(); ++i)
	{
		if (!(*i)) 				continue; // def not created
		if (!(*i)->useCount) 	continue; // def not used in scene;
		defsToBuild.push_back((*i));
	}

	
	std::string group = Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;  

	


	FontPtr f = FontManager::getSingleton().getByName("BlueHighway");
	Font *font = f.getPointer();
	if (!font) _asm int 3;

	mAtlas = Ogre::TextureManager::getSingleton().create(this->mTextureListFile+"atlas",group,true);
	mAtlas->setWidth(2048);
	mAtlas->setHeight(2048);
	mAtlas->setFormat(Ogre::PixelFormat::PF_R8G8B8A8);
	mAtlas->setNumMipmaps(1);
	mAtlas->createInternalResources();
	HardwarePixelBufferSharedPtr dest = mAtlas->getBuffer(0,0);
	PixelBox intermediate(128,128,dest->getDepth(),mAtlas->getFormat(),new char[128*128*4]); 
	Image::Box coordinates(0,0,128,128);
	RenderSystem *sys = Ogre::Root::getSingleton().getRenderSystem();

	//TexturePtr whitetex = Ogre::TextureManager::getSingleton().getByName("white128.png");
	TexturePtr whitetex = Ogre::TextureManager::getSingleton().load("white128.png",group);
	PixelBox BGCopy(128,128,dest->getDepth(),mAtlas->getFormat(),new char[128*128*4]); 
	HardwarePixelBufferSharedPtr white = whitetex->getBuffer(0,0);
	white->blitToMemory(BGCopy);

	for (i = defsToBuild.begin(); i != defsToBuild.end(); ++i)
	{
		(*i)->u = ((float)coordinates.left+sys->getHorizontalTexelOffset())/(float)dest->getWidth(); 
		(*i)->v = ((float)coordinates.top+sys->getVerticalTexelOffset())/(float)dest->getHeight();
		(*i)->w = (128.0f-sys->getHorizontalTexelOffset())/(float)dest->getWidth();
		(*i)->h = (128.0f-sys->getVerticalTexelOffset())/(float)dest->getHeight();
		if (!(*i)->mSource.isNull())
		{
			if ((*i)->mSource->getFormat() != mAtlas->getFormat())
			{
				(*i)->mSource->unload();
				(*i)->mSource->setFormat(mAtlas->getFormat());
				(*i)->mSource->load();
				std::string message = "Invalid pixel format for file: " + (*i)->mSource->getName();
				Main::Logger::getSingleton().logMessage(message,std::string("MaterialAtlas::build"));
		//		continue;
			}	
			Ogre::HardwarePixelBufferSharedPtr source =(*i)->mSource->getBuffer(0,0);
			source->blitToMemory(intermediate);
			dest->blitFromMemory(intermediate,coordinates); 
		}
		else
		{
			dest->blitFromMemory(BGCopy,coordinates); 
			WriteToTexture((*i)->name,dest,coordinates,font,ColourValue::Black,'c',false); 
		}
	
		
		if (coordinates.right + 128 > mAtlas->getWidth())
		{
			coordinates.top += 128;
			coordinates.bottom += 128;
			coordinates.left = 0;
			coordinates.right = 128;
		}
		else
		{
			coordinates.left += 128;
			coordinates.right += 128;
		}
	}
	delete[] intermediate.data;
	delete[] BGCopy.data;
	int k;
	

	
#if _DEBUG_SAVE_ATLAS
	Ogre::Image image;
	
	image.loadDynamicImage((Ogre::uchar  *)dest->lock(0,dest->getSizeInBytes(),Ogre::HardwareBuffer::LockOptions::HBL_READ_ONLY),dest->getWidth(),dest->getHeight(),dest->getDepth(),dest->getFormat());
	
	image.save(this->mTextureListFile + ".png");  
	dest->unlock();
#endif 

#if 0
	try 
	{
		mBlendMap = Ogre::TextureManager::getSingleton().load("blendMap.png",group);
	}
	catch(Ogre::FileNotFoundException)
	{
		this->generateBlendMap(); 
	}
	catch(...)
	{
		_asm int 3;
	}
#endif
	// create base material

	Ogre::MaterialPtr original = Ogre::MaterialManager::getSingleton().load("Blocks/Base_Atlas",group);
	this->mBaseMaterial = original->clone(this->mTextureListFile+"_material");
	Ogre::Material::TechniqueIterator iter = mBaseMaterial->getTechniqueIterator();
	while (iter.hasMoreElements())
	{
		Ogre::Technique *t =iter.getNext(); 
		Ogre::Technique::PassIterator piter = t->getPassIterator();
		while (piter.hasMoreElements())
		{
			Ogre::Pass *p = piter.getNext();
			p->getTextureUnitState(0)->setTextureName(this->mAtlas->getName());  
		}
	}
#if 0
	// create blend material
	original = Ogre::MaterialManager::getSingleton().load("Blocks/Blend_Atlas",group);
	this->mBlendMaterial = original->clone(this->mTextureListFile+"_blend");
	iter = mBlendMaterial->getTechniqueIterator();
	while (iter.hasMoreElements())
	{
		Ogre::Technique *t =iter.getNext(); 
		Ogre::Technique::PassIterator piter = t->getPassIterator();
		while (piter.hasMoreElements())
		{
			Ogre::Pass *p = piter.getNext();
			p->getTextureUnitState(0)->setTextureName(this->mAtlas->getName());  
		}
	}
#endif
}


MaterialDef * MaterialAtlas::getMaterialDefByName(std::string &name)
{
	std::vector<MaterialDef*>::iterator i;
	int k = 0;
	for (i = mTextures.begin(); i != mTextures.end() ; ++i,k++)
	{
		if ((*i) && (*i)->name == name) return (*i);
	}
	return 0;
}

WORD MaterialAtlas::getMaterialIndexByName(std::string &name)
{
	std::vector<MaterialDef*>::iterator i;
	WORD k = 0;
	for (i = mTextures.begin(); i != mTextures.end() ; ++i)
	{
		if ((*i) && (*i)->name == name) return k;
		k++;
	}
	return 0xFFFF;
}

MaterialDef * MaterialAtlas::getOrCreateMaterialDef(size_t index)
{
	if (index >= mTextures.size())
		return 0;
	if (mTextures.at(index) == 0) 
	{
		MaterialDef *def = new MaterialDef;
		def->name = mDefinitions.at(index)->mCapsname.c_str();
		def->useCount = 0;
		mTextures.at(index) = def;
		return def;
	}
	return mTextures.at(index);
}



MatglossXMLHandler ::MatglossXMLHandler (std::hacked_vector<DwarfFort::Matgloss*> &definitions,
										 std::vector<MaterialDef *> &output)
:mDefinitions(definitions),mOutput(output),mCurrentDef(0)
{
	mOutput.resize(mDefinitions.size());
}


void MatglossXMLHandler::elementStart(const CEGUI::String& element, const CEGUI::XMLAttributes& attributes)
{
    mElement="";
	if (element == "Materials") return;
	if (element == "Material")
	{
		if (attributes.exists("name"))
		{
			std::hacked_vector<DwarfFort::Matgloss*>::iterator i;

			std::string name = attributes.getValueAsString("name").c_str();
			int j=0; // offset from the start of the vector
			for (i = mDefinitions.begin(); i != mDefinitions.end(); ++i,j++)
			{
				if (name == (*i)->mCapsname.c_str())
					break;	// we found our match
			}
			if (i == mDefinitions.end())
			{
				mCurrentDef = new MaterialDef;
				mCurrentDef->useCount = 0;
				mOutput.push_back(mCurrentDef);
				mCurrentDef->name = name; // add additional materials at the end of the atlas
				
			}
			else
			{
				mCurrentDef = new MaterialDef;
				mCurrentDef->useCount = 0;
				mOutput.at(j) = mCurrentDef;
				mCurrentDef->name = name;
			}
		}
		else
			mCurrentDef = NULL;
	}
	if (!mCurrentDef) return;

	mElement = element.c_str();
}
void MatglossXMLHandler::elementEnd(const CEGUI::String& element)
{
	if (element == "Materials")
	{
		// add default material at the end
		MaterialDef *defaultMaterial = new MaterialDef;
		std::string group = Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;  
		defaultMaterial->mSource = Ogre::TextureManager::getSingleton().load("default_material.png",group);
		defaultMaterial->name = "default";
		mOutput.push_back(defaultMaterial);
	}
	if (!mCurrentDef) return;	
}
void MatglossXMLHandler::text(const CEGUI::String& text)
{
	if (!mCurrentDef) return;	
	if (mElement == "Texture")
	{
		std::string wrap = text.c_str();
		std::string group = Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME;  
		mCurrentDef->mSource = Ogre::TextureManager::getSingleton().load(wrap,group);
        mElement="";
	}
	
}